Android 项目架构之 MVP

需求

本文不是学术文字讨论(复制粘贴)文,所以不会详细解释什么叫 MVP,MVC,Dagger,Clean 等等。主要以实际项目为基础,简单的实现登录功能。

当代码量越来越大,需要对已有的代码进行整理以及重构,实现解耦.

无论任何的架构模式以及设计模式都是为了代码解耦

研究方向

MVP 模式:

  • Model:
    数据层,主要负责网络数据请求与获取,数据库的处理等等数据相关的逻辑处理
  • View:
    视图层,显示数据,例如 Activity,Fragment,View 等等 UI 载体
  • Presenter:
    代理层, View 逻辑处理的集合,并且将 Model 获取的数据返回给 View 层

MVP 类型

实现

MVP 的简单实现

逻辑图

mvp.png

  • View
    UI 的展示
  • BasePresenterImpl
    封装了 View 的处理流程,数据回调等等,例如进行网络请求的时候,loading 的显示与隐藏。
  • LoginPresenter
    登录逻辑处理的地方
  • BaseRespository
    封装了异常处理,例如网络请求回调的时候,页面消失了,这时候再进行 ui 处理的话就会奔溃。
    为了不每个页面都加上判空处理,所以就封装了一层。如不需要可以去掉这一层
  • LoginRespository
    登录真正执行的地方

关键代码

MainActivity.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class MainActivity extends Activity implements IBaseView<String> {

LoginPresenter loginPresenter;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
loginPresenter = new LoginPresenter(this);
loginPresenter.toLogin("111", "111");
}

@Override
protected void onDestroy() {
loginPresenter.unBind();
super.onDestroy();
}

@Override
public void onSuccess(String Result) {
Log.i("MainActivity", Result);

}

@Override
public void onFailed(ErrorCode errorCode) {
Log.i("MainActivity", errorCode.getMessage());
String msg = null;
msg.toLowerCase();
}

@Override
public void showProgress() {
Log.i("MainActivity", "loading");
}

@Override
public void hideProgress() {
Log.i("MainActivity", "hide loading");
}
}

LoginPresenter.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class LoginPresenter extends BasePresenterImpl {

IBaseRepository baseRepository;

public LoginPresenter(IBaseView mView) {
super(mView);
}

public void toLogin(String uid, String password) {
mView.showProgress();
baseRepository = LoginRepository.Builder(baseCallback)
.setUid(uid).setPassword(password);
baseRepository.Action();
}
}

LoginRepository.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
public class LoginRepository extends BaseRepository implements IBaseRepository {

private String uid;

private String password;

public static LoginRepository Builder(BaseInteractor.Callback callback) {
return new LoginRepository(callback);
}

public String getUid() {
return uid;
}

public LoginRepository setUid(String uid) {
this.uid = uid;
return this;
}

public String getPassword() {
return password;
}

public LoginRepository setPassword(String password) {
this.password = password;
return this;
}

public LoginRepository(BaseInteractor.Callback callback) {
super(callback);
}

@Override
public void Action() {
// TODO
// 网络请求
// onRequsetSuccess("login success");

onRequestFailed(new ErrorCode().setStatus(0).setMessage("login fail"));

}

@Override
public void Cancel() {
// TODO
// 取消网络请求

// 回调上一次进行异常处理
onCancel();
}
}

完整代码已经上传到github上去了 -> https://github.com/rinfon/mvp.git

MVP-Clean

逻辑图

关键代码

在研究官方的Demo中,有些地方还是值得斟酌的

  • View 层不通用,例如有些 loading,onSuccess,onError 等通用函数应该抽象到 BaseView 里面去,减少代码量
  • 所有的 Task 都在一开始就在界面初始化好了,如果有些功能我没有用到,那么就浪费资源了,应该改成需要用到的时候再初始化
  • 在 presenter 初始化的时候,View 层传的参数太多了。例如 View 层所有的 task 都要初始化好传进去。个人认为这是不科学的。但后来想官方这样写的原因是为了区分 View 与 Presenter 的职责(看来我还是太年轻了),View 作为数据的提供者,Presenter 作为数据的接受者。所以才不在 Presenter 层新建数据。既然如此,那只需要 Builder 模式就可以解决参数过多的问题了。
  • Presenter 层 callback 重复代码过多,例如
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
mUseCaseHandler.execute(loginTask, new LoginTask.LoginRequestValues(uid, password),
new UseCase.UseCaseCallback<BaseResponseValues>() {

@Override
public void onSuccess(BaseResponseValues response) {
mView.closeLoading();
mView.onSuccess(response);
}

@Override
public void onError() {
mView.closeLoading();
mView.onFail();
}
});

每次 excute 都需要提供一个 callback,而每个 callback 仅仅 response 的类型不一样而已。流程还是一样的。所以重复代码还是很多。

原本想在 UserCaseHandler 里面增加 View 的持有,从而在 UserCaseHandler 进行 CallBack 的时候把 closeloading 和 onSucces,onFail 都写好,这样就可以解决问题了。

这样的设计方案有个弊端,View 层入侵了 UserCaseHandler,耦合度又增加了。所以看实际需求来权衡吧

来来来,上代码

MvpCleanActivity.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public class MvpCleanActivity extends Activity implements LoginContract.View<BaseResponseValues> {

CleanLoginPresenter loginPresenter;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
loginPresenter = new CleanLoginPresenter(this, UseCaseHandler.getInstance(), new LoginTask());
loginPresenter.login("111", "111");

}

@Override
protected void onDestroy() {
super.onDestroy();
}

@Override
public void showLoading() {
Log.i("MvpCleanActivity", "showloading");

}

@Override
public void closeLoading() {
Log.i("MvpCleanActivity", "closeloading");

}

@Override
public void onSuccess(BaseResponseValues r) {
Log.i("MvpCleanActivity", r.getResult().getMessage());
// will crash
String msg = null;
msg.toLowerCase();
}

@Override
public void onFail() {
Log.i("MvpCleanActivity", "login fail");
}

CleanLoginPresenter.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class CleanLoginPresenter implements LoginContract.Presenter {

private final LoginContract.View mView;

private final UseCaseHandler mUseCaseHandler;

private final LoginTask loginTask;

public CleanLoginPresenter(LoginContract.View mView, UseCaseHandler mUseCaseHandler, LoginTask loginTask) {
// TODO 参数过多,使用 builder 模式
this.mView = mView;
this.mUseCaseHandler = mUseCaseHandler;
this.loginTask = loginTask;
}

@Override
public void login(String uid, String password) {
mView.showLoading();
mUseCaseHandler.execute(loginTask, new LoginTask.LoginRequestValues(uid, password),
new UseCase.UseCaseCallback<BaseResponseValues>() {

@Override
public void onSuccess(BaseResponseValues response) {
mView.closeLoading();
mView.onSuccess(response);
}

@Override
public void onError() {
mView.closeLoading();
mView.onFail();
}
});
}

LoginTask.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class LoginTask extends UseCase<LoginTask.LoginRequestValues, BaseResponseValues> {


@Override
protected void executeUseCase(LoginRequestValues requestValues) {
// TODO 网络请求

// 回调
getUseCaseCallback().onSuccess(new BaseResponseValues(
ResponseEntry
.Builder()
.setStatus(1)
.setMessage("Login success")));

}

public static final class LoginRequestValues extends BaseRequsetValues {

public LoginRequestValues(@NonNull String uid, @NonNull String password) {
super(new HashMap<String, String>());
uid = checkNotNull(uid, "task cannot be null!");
password = checkNotNull(password, "task cannot be null!");
getParmas().put("uid", uid);
getParmas().put("password", password);
}
}
}

LoginTask 里面跟官方还是不一样的,因为实际需求中,请求的参数和返回的结果结构大多数都是一样的,所以 RequestValues 和 ResponseValues 还是可以封装成公用的。

LoginContract.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class LoginContract {
public interface View<T> {
void showLoading();

void closeLoading();

void onSuccess(T Result);

void onFail();
}

public interface Presenter {
void login(String uid, String password);
}
}

就像本文所说,View 层可以再封装成 BaseView,把重复的的代码放进去

UserCase
相关的就不放出来了,官方已经封装好了,实际上就是封装了一个线程池,用于异步处理 Task,所以直接使用即可。
完整代码已经上传到 github上去了 -> https://github.com/rinfon/mvp.git

MVP-Dagger

未完待续

MVP-RxJava

RxJava ,之前我一直很抗拒,因为上手感觉很难,另一部分可能自己心燥,静不下来好好研究。但最近看 MVP-clean 的时候有用到,所以也就铁下心来研究了一下。

本来想写篇文章来分享一下的,但后来看到大神们都已经解释的很清楚,连我都能看懂的,我相信你们都能看懂的。

附上链接 ,小水管 RxJava 通过作者的小水管,我相信你们也会喜欢上 RxJava 的。

回到整体

MVP-RxJava,我看了 google 的 demo 之后,发现其实没什么特别的神的地方(应该是没什么出彩的地方),也就是简单版的 MVP 加上 RxJava 而已,所以 demo 就不放出来了。放出关键代码吧

LoginPresenter.Java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
mCompositeDisposable.add(Observable.create(new ObservableOnSubscribe<User>() {
@Override
public void subscribe(ObservableEmitter<User> e) throws Exception {
User user = new User();
user.uid = uid;
user.password = password;
e.onNext(user);
e.onComplete();
}
}).subscribeOn(mSchedulerProvider.io())
.observeOn(mSchedulerProvider.ui())
.subscribe(new Consumer<User>() {
@Override
public void accept(User user) throws Exception {
mView.closeLoading();
mView.onSuccess(user);
}
}, new Consumer<Throwable>() {
@Override
public void accept(Throwable throwable) throws Exception {
mView.closeLoading();
mView.onFail();
}
}));

总结

  • 任何架构设计都是为了解耦,而解耦随之带来的就是代码的增加以及重复
  • 设计的过程中,要注意各层的职责,尽量可以避免入侵
  • 在我的 Demo 中,为了处理回调导致的奔溃,我对回调进行了 try catch,这没有问题,但要对详细的异常进行区分,不能所有的异常都吃掉,这是一种不负责的写法(小孩子不要模范啦)有时候,就应该让它 Crash,才知道问题所在。如果非要吃掉所有的异常,也要封装成一个 ErrorCode,对错误信息进行收集。知乎链接,里面有大牛解释的很清楚了。
-------------本文结束感谢您的阅读-------------
您的支持将是我最大的动力